package org.lw;

//import lombok.extern.slf4j.Slf4j;
import org.apache.commons.codec.binary.Base64;
import org.bouncycastle.asn1.ASN1EncodableVector;
import org.bouncycastle.asn1.ASN1Integer;
import org.bouncycastle.asn1.ASN1Sequence;
import org.bouncycastle.asn1.DERSequence;
import org.bouncycastle.asn1.gm.GMNamedCurves;
import org.bouncycastle.asn1.x9.X9ECParameters;
import org.bouncycastle.crypto.digests.SM3Digest;
import org.bouncycastle.crypto.params.ECDomainParameters;
import org.bouncycastle.jcajce.provider.asymmetric.ec.BCECPrivateKey;
import org.bouncycastle.jcajce.provider.asymmetric.ec.BCECPublicKey;
import org.bouncycastle.jcajce.spec.SM2ParameterSpec;
import org.bouncycastle.jce.provider.BouncyCastleProvider;
import org.bouncycastle.jce.spec.ECParameterSpec;
import org.bouncycastle.jce.spec.ECPrivateKeySpec;
import org.bouncycastle.jce.spec.ECPublicKeySpec;
import org.bouncycastle.math.ec.ECPoint;
import org.bouncycastle.util.encoders.Hex;
//import org.junit.Test;

import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.math.BigInteger;
import java.security.*;
import java.security.spec.ECGenParameterSpec;
import java.util.Arrays;


//@Slf4j
public class SmUtil {

    private final static int RS_LEN = 32;
    private static X9ECParameters x9ECParameters = GMNamedCurves.getByName("sm2p256v1");
    private static ECDomainParameters ecDomainParameters = new ECDomainParameters(x9ECParameters.getCurve(), x9ECParameters.getG(), x9ECParameters.getN());
    private static ECParameterSpec ecParameterSpec = new ECParameterSpec(x9ECParameters.getCurve(), x9ECParameters.getG(), x9ECParameters.getN());

    static {
        if (Security.getProvider("BC") == null) {
            Security.addProvider(new BouncyCastleProvider());
        }
    }

    /**
     * 生成sm3摘要
     *
     * @param src
     * @return
     * @throws UnsupportedEncodingException
     */
    public static String generateSM3HASH(String src) throws UnsupportedEncodingException {
        byte[] md = new byte[32];
        byte[] msg1 = src.getBytes("utf-8");
        SM3Digest sm3 = new SM3Digest();
        sm3.update(msg1, 0, msg1.length);
        sm3.doFinal(md, 0);
        String s = new String(Hex.encode(md)).toUpperCase();
//        log.debug("SM3摘要：{}", s);
        return s;
    }

    public static String signSm3WithSm2(String msg, String userId, String privateKey) throws Exception {
        BCECPrivateKey bcecPrivateKey = getPrivatekeyFromD(privateKey);
        String sign = Base64.encodeBase64String(signSm3WithSm2(msg.getBytes("utf-8"), userId.getBytes("utf-8"), bcecPrivateKey));
//        log.debug("生成的签名：{}", sign);
        return sign;
    }

    public static BCECPrivateKey getPrivatekeyFromD(String privateKey) {
        ECPrivateKeySpec ecPrivateKeySpec = new ECPrivateKeySpec(new BigInteger(privateKey, 16), ecParameterSpec);
        return new BCECPrivateKey("EC", ecPrivateKeySpec, BouncyCastleProvider.CONFIGURATION);
    }

    /**
     * @param msg
     * @param userId
     * @param privateKey
     * @return r||s，直接拼接byte数组的rs
     */
    public static byte[] signSm3WithSm2(byte[] msg, byte[] userId, PrivateKey privateKey) {
        return rsAsn1ToPlainByteArray(signSm3WithSm2Asn1Rs(msg, userId, privateKey));
    }

    /**
     * @param msg
     * @param userId
     * @param privateKey
     * @return rs in <b>asn1 format</b>
     */
    public static byte[] signSm3WithSm2Asn1Rs(byte[] msg, byte[] userId, PrivateKey privateKey) {
        try {
            SM2ParameterSpec parameterSpec = new SM2ParameterSpec(userId);
            Signature signer = Signature.getInstance("SM3withSM2", "BC");
            signer.setParameter(parameterSpec);
            signer.initSign(privateKey, new SecureRandom());
            signer.update(msg, 0, msg.length);
            byte[] sig = signer.sign();
            return sig;
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }

    /**
     * BC的SM3withSM2签名得到的结果的rs是asn1格式的，这个方法转化成直接拼接r||s
     *
     * @param rsDer rs in asn1 format
     * @return sign result in plain byte array
     */
    private static byte[] rsAsn1ToPlainByteArray(byte[] rsDer) {
        ASN1Sequence seq = ASN1Sequence.getInstance(rsDer);
        byte[] r = bigIntToFixexLengthBytes(ASN1Integer.getInstance(seq.getObjectAt(0)).getValue());
        byte[] s = bigIntToFixexLengthBytes(ASN1Integer.getInstance(seq.getObjectAt(1)).getValue());
        byte[] result = new byte[RS_LEN * 2];
        System.arraycopy(r, 0, result, 0, r.length);
        System.arraycopy(s, 0, result, RS_LEN, s.length);
        return result;
    }

    private static byte[] bigIntToFixexLengthBytes(BigInteger rOrS) {
        // for sm2p256v1, n is 00fffffffeffffffffffffffffffffffff7203df6b21c6052b53bbf40939d54123,
        // r and s are the result of mod n, so they should be less than n and have length<=32
        byte[] rs = rOrS.toByteArray();
        if (rs.length == RS_LEN) {
            return rs;
        } else if (rs.length == RS_LEN + 1 && rs[0] == 0) {
            return Arrays.copyOfRange(rs, 1, RS_LEN + 1);
        } else if (rs.length < RS_LEN) {
            byte[] result = new byte[RS_LEN];
            Arrays.fill(result, (byte) 0);
            System.arraycopy(rs, 0, result, RS_LEN - rs.length, rs.length);
            return result;
        } else {
            throw new RuntimeException("err rs: " + Hex.toHexString(rs));
        }
    }


    /**
     * @param msg
     * @param userId
     * @param rs 签名
     * @param publicKey
     * @return
     */
    public static boolean verifySm3WithSm2(String msg, String userId, String rs, String publicKey) throws Exception {
        return verifySm3WithSm2(msg.getBytes("utf-8"), userId.getBytes("utf-8"), Base64.decodeBase64(rs), getBCECPublicKeyFromD(publicKey));
    }

    /**
     * @param msg
     * @param userId
     * @param rs        r||s，直接拼接byte数组的rs
     * @param publicKey
     * @return
     */
    public static boolean verifySm3WithSm2(byte[] msg, byte[] userId, byte[] rs, PublicKey publicKey) {
        return verifySm3WithSm2Asn1Rs(msg, userId, rsPlainByteArrayToAsn1(rs), publicKey);
    }

    /**
     * BC的SM3withSM2验签需要的rs是asn1格式的，这个方法将直接拼接r||s的字节数组转化成asn1格式
     *
     * @param sign in plain byte array
     * @return rs result in asn1 format
     */
    private static byte[] rsPlainByteArrayToAsn1(byte[] sign) {
        if (sign.length != RS_LEN * 2) {
            throw new RuntimeException("err rs. ");
        }
        BigInteger r = new BigInteger(1, Arrays.copyOfRange(sign, 0, RS_LEN));
        BigInteger s = new BigInteger(1, Arrays.copyOfRange(sign, RS_LEN, RS_LEN * 2));
        ASN1EncodableVector v = new ASN1EncodableVector();
        v.add(new ASN1Integer(r));
        v.add(new ASN1Integer(s));
        try {
            return new DERSequence(v).getEncoded("DER");
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }

    /**
     * @param msg
     * @param userId
     * @param rs        in <b>asn1 format</b>
     * @param publicKey
     * @return
     */
    public static boolean verifySm3WithSm2Asn1Rs(byte[] msg, byte[] userId, byte[] rs, PublicKey publicKey) {
        try {
            SM2ParameterSpec parameterSpec = new SM2ParameterSpec(userId);
            Signature verifier = Signature.getInstance("SM3withSM2", new BouncyCastleProvider());
            verifier.setParameter(parameterSpec);
            verifier.initVerify(publicKey);
            verifier.update(msg, 0, msg.length);
            return verifier.verify(rs);
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }

    /**
     * 转成公钥对象
     *
     * @param pubKey
     * @return
     */
    public static PublicKey getBCECPublicKeyFromD(String pubKey) throws Exception {
        //TODO 不知道为什么在公钥前加04才不会有问题
        final String prefix = "04";
        if (!pubKey.startsWith(prefix)) {
            pubKey = prefix + pubKey;
        }
        // 将公钥HEX字符串转换为椭圆曲线对应的点
        ECPoint ecPoint = x9ECParameters.getCurve().decodePoint(Hex.decode(pubKey));
        ECPublicKeySpec eCPublicKeySpec = new ECPublicKeySpec(ecPoint, ecParameterSpec);
        return new BCECPublicKey("EC", eCPublicKeySpec, BouncyCastleProvider.CONFIGURATION);


    }



//    /**
//     * 国密公私钥生成
//     *
//     * @throws NoSuchAlgorithmException
//     * @throws InvalidAlgorithmParameterException
//     */
//    @Test
//    public void test11() throws NoSuchAlgorithmException, InvalidAlgorithmParameterException {
//        // 获取SM2椭圆曲线的参数
//        final ECGenParameterSpec sm2Spec = new ECGenParameterSpec("sm2p256v1");
//        // 获取一个椭圆曲线类型的密钥对生成器
//        final KeyPairGenerator kpg = KeyPairGenerator.getInstance("EC", new BouncyCastleProvider());
//        // 使用SM2参数初始化生成器
//        kpg.initialize(sm2Spec);
//
//        // 使用SM2的算法区域初始化密钥生成器
//        kpg.initialize(sm2Spec, new SecureRandom());
//        // 获取密钥对
//        KeyPair keyPair = kpg.generateKeyPair();
//
//        log.info(Hex.toHexString(((BCECPrivateKey) keyPair.getPrivate()).getD().toByteArray()));
//        log.info(Hex.toHexString(((BCECPublicKey) keyPair.getPublic()).getQ().getEncoded(false)));
//    }

}
